# -*- coding: utf-8 -*-
from functools import wraps
import string
import hashlib, binascii
import coloredlogs
import logging
import os.path
from logging import info, error, debug
from os.path import splitext
import base64
from jose import jwt
import peewee
import redis
from flask import Flask, redirect, request
from flask_mysqldb import MySQL
from playhouse.db_url import connect
import time
import random
import json
from playhouse.shortcuts import RetryOperationalError
import dip
import pbcvt
import matplotlib.pyplot as plt
import numpy as np
import scipy
import scipy.misc
from jose import JWTError

random.seed(time.time())
import datetime

# FORMAT = '  [%(levelname)-5s] %(process)5d:%(asctime)-8s %(filename)s:%(lineno)d %(message)s'
# DATEFORMAT = '%H:%M:%S'
# logging.basicConfig(level=logging.INFO, format=FORMAT, datefmt=DATEFORMAT)

app = Flask(__name__, static_url_path='/static')
# mysql = MySQL(app)

# configure upload parameter
UPLOAD_FOLDER = 'static/uploads'
ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'}
if not os.path.exists(UPLOAD_FOLDER):
    os.mkdir(UPLOAD_FOLDER)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER


# mysql_db = peewee.MySQLDatabase('mysql+pool://root:1234@mysql:3306/deepdr')
class MyRetryDB(RetryOperationalError, peewee.MySQLDatabase):
    pass


# db = MyRetryDB('my_app')
mysql_db = MyRetryDB(host="mysql", user="root",
                     passwd="1234", database="deepdr", charset='utf8')
# mysql_db = connect('mysql+pool://root:1234@mysql:3306/deepdr')

# set up redis
pool = redis.ConnectionPool(host='localhost', port=6379, db=0)


class BaseModel(peewee.Model):
    class Meta:
        database = mysql_db


class Patient(BaseModel):
    # id will be automatically added here
    name = peewee.TextField(null=True)
    gender = peewee.TextField(null=True)
    age = peewee.IntegerField(default=0)
    eye_sight = peewee.TextField(null=True)
    created_date = peewee.DateTimeField(default=datetime.datetime.now)


class FundusImage(BaseModel):
    # id will be automatically added here
    #  patient_id here is the id in database, not id in the hospital
    patient_id = peewee.IntegerField(default=-1)
    filename = peewee.TextField()
    original_name = peewee.TextField(default='not given')
    created_date = peewee.DateTimeField(default=datetime.datetime.now)
    which_eye = peewee.TextField(default='not given')
    result_raw = peewee.TextField(default='invalid')


class Check_sys(BaseModel):
    # id will be automatically added here
    #  patient_id here is the id in database, not id in the hospital
    name = peewee.TextField(default='Not Given')
    number = peewee.TextField(default='未识别')
    created_date = peewee.TextField(default='未识别')
    which_eye = peewee.TextField(default='未识别')
    # 0-initial, confirmed, changed by doctor
    confirmed = peewee.IntegerField(default=0)
    # 0 nchanged, 1 changed
    changed = peewee.IntegerField(default=0)
    original_image = peewee.TextField(default='')
    # 0: No, 1, suspect, 2 several
    degree = peewee.IntegerField(default=0)
    # 0,1,2,3,4, NO, slight, mid, several, PDR
    level = peewee.IntegerField(default=0)
    # 0: good, 1: effect diagnose, 2: unable to diagnose
    image_quality = peewee.IntegerField(default=0)
    result = peewee.TextField(default="")
    ha_img = peewee.TextField(default='')
    ha_area = peewee.FloatField(default=0)
    ex_img = peewee.TextField(default='')
    ex_area = peewee.FloatField(default=0)
    eye_sight = peewee.TextField(default='')
    high_blood_pressure = peewee.IntegerField(default=0)
    diabetes = peewee.IntegerField(default=0)
    age = peewee.IntegerField(default=-1)
    none_dr_lesion = peewee.TextField(default="无")


class User(BaseModel):
    user_name = peewee.CharField(null=False, max_length=128, unique=True)
    password = peewee.CharField(null=False,max_length=128)
    salt = peewee.CharField(null=False, max_length=128)
    is_admin = peewee.IntegerField(default=0)
    last_login = peewee.TimeField(default=datetime.datetime.now)


def gen_random_str(length=64):
    return ''.join(random.choice(string.ascii_letters + string.digits)
                   for _ in range(length))


def hash_password(psw: str, salt: str = ''):
    if salt == '':
        salt = gen_random_str()
    dk = hashlib.pbkdf2_hmac('sha256', bytearray(psw, 'ascii'), bytearray(salt, 'ascii'), 100000)
    hashed_psw = binascii.hexlify(dk)
    psw = hashed_psw.decode('ascii')
    return psw


def log_request(func):
    def decorator():
        debug(request.json)
        return func()

    return decorator


def add_user(uname, psw, is_admin=0):
    salt = gen_random_str()
    psw = hash_password(psw, salt)
    record, succ = User.create_or_get(
        user_name=uname,
        password=psw,
        salt=salt,
        is_admin=is_admin,
    )
    if not succ:
        return False, 'User already existed'
    return True, 'OK'


JWT_KEY = gen_random_str(10)

def get_token(uname, _psw):
    try:
        rec = User.get(User.user_name == uname)
    except peewee.DoesNotExist as dne:
        return False, 'User Does Not Exist'
    debug(rec.user_name)
    debug(rec.password)
    psw = hash_password(_psw, rec.salt)
    if rec.password != psw:
        return False, 'Error password'
    rec.last_login = datetime.datetime.now().time()
    rec.save()
    token_ = jwt.encode({
        'user': uname,
        'last_login': time.time(),
    }, JWT_KEY, algorithm='HS256')
    return True, token_


def check_token(token_str):
    try:
        token_info = jwt.decode(token_str, JWT_KEY, algorithms=['HS256'])
        return token_info
    except JWTError as jerr:
        error(jerr)
        return None


def need_token(func):
    def decorator():
        debug('checking token.')
        token = ''
        if not (request.json is None) and 'token' in request.json.keys():
            token = request.json['token']
        elif 'token' in request.form.keys():
            debug('fuck')
        else:
            error('No token')
        debug('checking token ' + token)
        tkinfo = check_token(token)
        debug(tkinfo)
        if tkinfo is None:
            return json.dumps({'Error': 'Token Err'})
        return func()

    return decorator


@app.route('/api/get_token', methods=['POST'])
def api_get_token():
    psw = request.json['password']
    user_name = request.json['user_name']
    succ, token = get_token(user_name,psw)
    retval = {
        'error': token if not succ else '',
        'token': token if succ else '',
    }
    return json.dumps(retval)


@app.route('/api/add_user')
def api_add_user():
    raise NotImplemented


@app.route('/api/confirm', methods=['POST'])
def confirm_list():
    debug(request.json)
    # add patient records
    for item in request.json:
        record = Check_sys.get(
            id=item['id']
        )
        record.changed = item['changed']
        record.degree = item['degree']
        record.level = item['level']
        record.image_quality = item["image_quality"]
        record.none_dr_lesion = item['none_dr_lesion']

        record.confirmed = 1
        record.save()
    return ' '


@app.route('/api/check_list/count/<int:confirmed>')
def check_list_count(confirmed):
    result = {
        'confirmed': confirmed,
        'count': Check_sys.select().where(Check_sys.confirmed == confirmed).count()
    }
    return json.dumps(result)


@app.route('/api/check_list/<int:confirmed>/<int:page>/<int:items_per_page>')
def check_list(confirmed, page, items_per_page):
    records = Check_sys.select(). \
        where(Check_sys.confirmed == confirmed). \
        order_by(Check_sys.number, Check_sys.which_eye). \
        paginate(page=page, paginate_by=items_per_page)
    result = []
    for rec in records:
        result.append({
            'id': rec.id,
            "name": rec.name,
            "number": rec.number,
            "created_date": rec.created_date,
            "which_eye": rec.which_eye,
            "confirmed": rec.confirmed,
            "changed": rec.changed,
            "original_image": rec.original_image,
            "degree": rec.degree,
            "level": rec.level,
            "image_quality": rec.image_quality,
            "result": rec.result,
            "ha_img": rec.ha_img,
            "ha_area": rec.ha_area,
            "ex_img": rec.ex_img,
            "ex_area": rec.ex_area,
            "eye_sight": rec.eye_sight,
            "high_blood_pressure": rec.high_blood_pressure,
            "diabetes": rec.diabetes,
            "age": rec.age,
            "none_dr_lesion": rec.none_dr_lesion if not (rec.none_dr_lesion is None) and len(
                rec.none_dr_lesion) else '无',

        })
    return json.dumps(result)


def get_new_file_name(original_file_name):
    ext = splitext(original_file_name)[1]
    while 1:
        fname = "%s/%015d%s" % (UPLOAD_FOLDER, random.randint(1, 1000000000000), ext)
        if os.path.exists(fname):
            continue
        return fname


mysql_db.connect()
mysql_db.create_tables([Patient, FundusImage, Check_sys, User], safe=True)


@app.route('/')
def hello_world():
    return redirect('/static/index.html')


def err_info(msg=''):
    return json.dumps({
        'err_info': msg,
        'err': True
    })


@app.route('/api/upload', methods=['POST'])
# @need_token
def upload():
    info('uploading file')
    if request.method == 'POST':
        # check if the post request has the file part
        if 'file' not in request.files:
            error('file not found')
            error(request.files)
            return err_info('file not found')
        file = request.files['file']
        # if user does not select file, browser also
        # submit a empty part without filename
        if file.filename == '':
            error('filename empty')
            return err_info('filename empty')
        info('upload file name' + file.filename)
        fname = get_new_file_name(file.filename)
        file.save(fname)
        debug(file.filename + ' save file to ' + fname)
        db_record = FundusImage.create(
            filename=fname,
            # original_name=file.filename,
            which_eye=request.form['which_eye']
        )
        db_record.save()
        retval = {
            'id': db_record.id,
            'url': '/' + fname,
        }
        return json.dumps(retval)
    else:
        error('request method is not post ' + request.method)
        return err_info('request method is not post but' + request.method)


@app.route('/api/add_patient', methods=["POST"])
def add_patient():
    debug(request.content_length)
    debug(request.json)
    # add patient records
    name = ''
    try:
        name = base64.encodebytes(bytes(request.json['name'].encode('utf-8'))).decode('utf-8')
    except:
        name = 'Not given'
    info(name)
    patient_record = Patient.create(
        name=name or 'not given',
        gender=request.json['gender'] or 'not given',
        patient_id=request.json['id'] or 'not given',
        age=request.json['age'] or -1,
        eye_sight=request.json['eye_sight'],
    )
    patient_record.save()
    r = redis.Redis(connection_pool=pool)
    for pic in request.json['pictures']:
        image_record = FundusImage.get(FundusImage.id == pic['id'])
        image_record.patient_id = patient_record.id
        image_record.save()
        task = {
            'id': image_record.id,
            'file_name': image_record.filename
        }
        r.rpush('diagnose_tasks', json.dumps(task))
    # add task
    # run_worker(r)
    return ''


@app.route('/api/get_patient/<patient_id>')
def get_patient(patient_id):
    patient_id = int(patient_id)
    debug('getting patient %d' % patient_id)
    patient_record = Patient.get(id=patient_id)
    retval = {
        'name': str(patient_record.name),
        'gender': str(patient_record.gender),
        'age': int(patient_record.age),
        'eye_sight': str(patient_record.eye_sight),
        'created_date': str(patient_record.created_date),
    }
    return json.dumps(retval)


@app.route('/api/get_image_list')
def get_image_list():
    retval = []
    for ele in FundusImage.select():
        debug(ele.result_raw)
        if ele.result_raw == 'invalid':
            result = {
                'ready': False
            }
        else:
            result = json.loads(ele.result_raw)
            result['ready'] = True
        patient = {}
        try:
            patient_record = Patient.get(id=ele.patient_id)
            name = base64.decodebytes(bytes(patient_record.name.encode('utf-8'))).decode('utf-8')
            patient = {
                'name': name,
                'gender': str(patient_record.gender),
                'age': int(patient_record.age),
                'eye_sight': str(patient_record.eye_sight),
                'created_date': str(patient_record.created_date),
            }
        except:
            pass
        retval.append({
            'filename': str(ele.filename),
            'original_name': str(ele.original_name),
            'created_date': str(ele.created_date),
            'which_eye': str(ele.which_eye),
            'result': result,
            'patient': patient,
        })
    return json.dumps(retval)


@app.route('/api/get_single_image/<img_id>')
def get_single_img(img_id):
    ele = FundusImage.get(id=img_id)
    result = {
        'ready': False
    }
    info(ele.result_raw)
    if ele.result_raw and ele.result_raw != "" and ele.result_raw != 'invalid':
        result = json.loads(ele.result_raw)
        result['ready'] = True
    info(ele.filename)
    patient_record = Patient.get(id=ele.patient_id)
    name = ''
    try:
        name = base64.decodebytes(bytes(patient_record.name.encode('utf-8'))).decode('utf-8')
    except:
        pass
    patient = {
        'name': name,
        'gender': str(patient_record.gender),
        'age': int(patient_record.age),
        'eye_sight': str(patient_record.eye_sight),
        'created_date': str(patient_record.created_date),
    }
    return json.dumps({
        'filename': ele.filename,
        'original_name': str(ele.original_name),
        'created_date': str(ele.created_date),
        'which_eye': str(ele.which_eye),
        'result': result,
        'patient': patient,
    })


if __name__ == '__main__':
    coloredlogs.install(level='DEBUG', logger=logging.getLogger(),
                        fmt='%(filename)6s:%(lineno)3d %(message)s',
                        datefmt='%H:%M:%S',
                        )
    add_user('dailing','12344',1)
    add_user('deepdr','$DEEPDR',1)
    _,token = get_token('dailing','12344')
    debug(token)
    token_info = check_token(token)
    debug(token_info)
    app.run(port=8080, host="0.0.0.0")
